如何实现 apply、call


apply 规范

实现 apply

第一版本 借助 call 或 bind

借助原生提供的 call、bind 方法来改变函数的 this 指向,这种方式比较简单

// 借助 call
Function.prototype.apply2 = function(content = window, args = []) {
  return this.call(content, ...args)
}

// 借助 bind
Function.prototype.apply3 = function(content = window, args = []) {
  return this.bind(content, ...args)()
}

const obj = {
  a: 100
}

function fn(a, b) {
  console.log(a + b)
  console.log(this.a)
}

fn.apply2(obj, [1, 2]) // 3 100
fn.apply3(obj, [1, 2]) // 3 100

第二版本

在传入的执行山下文对象(content)中动态创建一个属性,这个属性指向函数,再调用刚才 content 中创建的方法,这样 this 就会执行调用该方法的对象,也就是传入的 content。JavaScript 中的 this 指向具体参考: JavaScript 中的 this

Function.prototype.apply2 = function(content = window, args = []) {
  content.fn = this
  const result = content.fn(...args)
  return result
}

第三个版本

次版本为上一个版本的改良版,主要是加上了异常判断,还有给传入的执行上下文动态添加方法的时候,借助的 es6 新增的 symbol 保证了 key 的唯一性。最后再将临时创建的方法删掉。

Function.prototype.apply2 = function(content = window, args = []) {
  if (typeof this !== 'function') {
    throw new Error(`${this} not a function`)
  }
  const fn = Symbol('fn')
  content[fn] = this
  const result = content(...args)
  delete content[fn]
  return result
}

实现 call

call 和 apply 类似,只是 call 接收的参数不同。bind 和 apply 第一个参数都是传入的执行上下文,而其余的函数参数,apply 是以参数数组的形式从第二个参数传入;call 则是第二个参数开始已参数的形式传入。

第一个版本:借助 apply 或 bind

// 借助 apply
Function.prototype.call2 = function(content = window, ...args) {
  return this.apply(content, args)
}

// 借助 bind
Function.prototype.call3 = function(content = window, ...args) {
  return this.bind(content, ...args)()
}

const obj = {
  a: 100
}

const a = 1
const b = 2

function fn(a, b) {
  console.log(a + b)
  console.log(this.a)
}

fn.call2(obj, a, b) // 3 100
fn.call3(obj, a, b) // 3 100

第二个版本 call

Function.prototype.call2 = function(content = window, ...args) {
  content.fn = this
  return content.fn(...args)
}

第三个版本 call

Function.prototype.call2 = function(content = window, ...args) {
  if (typeof this !== 'function') {
    throw new Error(`${this} not a function`)
  }
  const fn = Symbol('fn')
  content[fn] = this
  const result = content[fn](...args)
  delete content[fn]
  return result
}